Lambda 関数を利用して、オンデマンドキャパシティ予約を実行してみた
はじめに
テクニカルサポートの 片方 です。
EC2 インスタンスを起動する際に、Insufficient Instance Capacity エラーが発生して(利用予定のインスタンスタイプのリソースが AWS 基盤でキャパシティー不足の状況)希望の EC2 インスタンスタイプが起動できないことがあります。
本エラーに関する回避方法としては複数ございますが、今回はその中の一つオンデマンドキャパシティ予約を Lambda 関数を利用して実行したいと思います。
その他の回避方法につきましては、弊社ブログをご確認ください。
実装してみた
以下の順番で実装します。
- 実行ロール作成
- Lambda 関数作成
注意点して、Insufficient Instance Capacity エラーは希望されるインスタンスタイプの在庫が AWS にて枯渇しておりオンデマンドキャパシティ予約事態もできないことがあるため、起動を確約するものではないです。
そのため、オンデマンドキャパシティー予約が失敗した際に通知する仕組みも合わせて Lambda 関数に実装します。
次のいずれかが当てはまる場合、キャパシティーの予約 を作成するリクエストは失敗する可能性があります。
・ Amazon EC2 には、リクエストに対応する十分なキャパシティ-がありません。時間をおいてからもう一度試すか、別のアベイラビリティーゾーンを試すか、リクエストを小さくしてみてください。インスタンスタイプとサイズに応じてアプリケーションに柔軟性がある場合は、別のインスタンス属性を試してみてください。
・ リクエストされた数量は、選択したインスタンスファミリーに対するオンデマンドインスタンスの上限を超えています。インスタンスファミリーに対するオンデマンドインスタンスの上限を上げて、もう一度試してください。詳細については、オンデマンドインスタンスクォータ を参照してください。
実行ロール
信頼関係
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
アタッチするポリシー例
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:<region>:<account-id>:*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:<region>:<account-id>:log-group:/aws/lambda/<lambda-function-name>:*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateCapacityReservation",
"ec2:DescribeCapacityReservations",
"ec2:DescribeInstances",
"ec2:DescribeInstanceTypes"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "SNS:Publish",
"Resource": "arn:aws:sns:<region>:<account-id>:<SNS-Topic-NAME>"
}
]
}
※ 適宜修正してください。
Lambda 関数
Python 3.12 で作成しました。
実行ロールでは、既存のロールを使用するを選択し、先ほど作成したロールを指定します。
実装する Lambda 関数例
import boto3
import datetime
import os
ec2_client = boto3.client('ec2', region_name='ap-northeast-1')
sns_client = boto3.client('sns')
def lambda_handler(event, context):
instance_type = os.environ.get('INSTANCE_TYPE')
instance_platform = os.environ.get('INSTANCE_PLATFORM')
availability_zone = os.environ.get('AVAILABILITY_ZONE')
instance_count = int(os.environ.get('INSTANCE_COUNT', 1))
end_hour = int(os.environ.get('END_HOUR', 17))
end_minute = int(os.environ.get('END_MINUTE', 0))
sns_topic_arn = os.environ.get('SNS_TOPIC_ARN')
if instance_type is None:
raise ValueError("INSTANCE_TYPE environment variable is not set.")
if instance_platform is None:
raise ValueError("INSTANCE_PLATFORM environment variable is not set.")
if availability_zone is None:
raise ValueError("AVAILABILITY_ZONE environment variable is not set.")
if sns_topic_arn is None:
raise ValueError("SNS_TOPIC_ARN environment variable is not set.")
now = datetime.datetime.utcnow()
japan_time = now + datetime.timedelta(hours=9)
end_time = japan_time.replace(hour=end_hour, minute=end_minute, second=0, microsecond=0)
if end_time < japan_time:
end_time += datetime.timedelta(days=1)
end_time_utc = end_time - datetime.timedelta(hours=9)
try:
describe_instance_types = ec2_client.describe_instance_types(InstanceTypes=[instance_type])
ebs_optimized_supported = describe_instance_types['InstanceTypes'][0]['EbsInfo']['EbsOptimizedSupport'] != 'unsupported'
except KeyError:
ebs_optimized_supported = False
create_capacity_reservation_params = {
"InstanceType": instance_type,
"InstancePlatform": instance_platform,
"AvailabilityZone": availability_zone,
"Tenancy": 'default',
"InstanceCount": instance_count,
"EndDate": end_time_utc,
"EndDateType": 'limited',
"InstanceMatchCriteria": 'open'
}
if ebs_optimized_supported:
create_capacity_reservation_params["EbsOptimized"] = True
try:
response = ec2_client.create_capacity_reservation(**create_capacity_reservation_params)
return {
'statusCode': 200,
'body': f'Capacity reservation created for {instance_count} instances until {end_time_utc} (UTC)'
}
except ec2_client.exceptions.ClientError as e:
error_message = f'Failed to create capacity reservation: {str(e)}'
sns_client.publish(
TopicArn=sns_topic_arn,
Subject='EC2 Capacity Reservation Failure',
Message=error_message
)
return {
'statusCode': 500,
'body': error_message
}
環境変数例
環境変数を使用しているのでキーは以下に、値は環境に合わせて設定してください。
キー | 値 |
---|---|
AVAILABILITY_ZONE | ap-northeast-1a |
END_HOUR | 23 |
END_MINUTE | 15 |
INSTANCE_COUNT | 2 |
INSTANCE_PLATFORM | Linux/UNIX |
INSTANCE_TYPE | g4dn.metal |
SNS_TOPIC_ARN | arn:aws:sns:<region>:<account-id>:<SNS-Topic-NAME> |
※ 適宜修正してください。
仮に、END_HOUR:23、END_MINUTE:15 とした場合、終了日時は Lambda 関数を実行したタイミングで異なります。
-
終了日時 23:15 より前に実行した場合
例えば 2024-10-31 17:00 に Lambda 関数を実行した際は、実行日の 2024-10-31 23:15 が設定されます。 -
終了日時 23:15 より後に実行した場合は
例えば 2024-10-31 23:45 に Lambda 関数を実行した際は、実行日の翌日 2024-11-01 23:15 が設定されます。
検証してみた
EBS 最適化インスタンスの有無に関わらず、オンデマンドキャパシティー予約可能であるか検証します。
- m5.large
- t2.micro
設定例
両設定についてテストします。
Lambda 関数実行は成功したので、オンデマンドキャパシティー予約されているか確認します。
成功しました!
次に、State(ステータス)が failed の際に、通知されるか確認します。
failed になるように、環境変数を設定します。
テストが成功したので、該当オンデマンドキャパシティー予約のステータスが failed になっているか確認します。
failed になっているので、SNS トピックで指定した送信先へ通知されているか確認します。
成功しました!
まとめ
Amazon EventBridge と組合せ、スケジュールに従って当該 Lambda 関数が実行されるルールを作成する方法などが有用であるかと思います。
本ブログが皆様の参考になれば幸いです。
参考資料
- EC2 起動時の Insufficient Instance Capacity エラーをオンデマンドキャパシティ予約で回避する | DevelopersIO
- キャパシティーの予約 の作成 - Amazon Elastic Compute Cloud
- create_capacity_reservation - Boto3 1.35.54 documentation
- Amazon でスケジュールに従って実行されるルールの作成 EventBridge - Amazon EventBridge
アノテーション株式会社について
アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。